Skip to content

Enable TBO Support & Fix Accuracy Regressions for Kimi K2.5 - #1369

Open
jpy794 wants to merge 10 commits into
ROCm:mainfrom
RadeonFlow:rf-dpa-tbo-rebase
Open

Enable TBO Support & Fix Accuracy Regressions for Kimi K2.5#1369
jpy794 wants to merge 10 commits into
ROCm:mainfrom
RadeonFlow:rf-dpa-tbo-rebase

Conversation

@jpy794

@jpy794 jpy794 commented Jun 26, 2026

Copy link
Copy Markdown

Motivation

Kimi K2.5 inference under Data-Parallel Attention (DPA) combined with Two-Batch Overlap (TBO) exposed several gaps that either crashed the engine or left performance on the table. This PR enables the DPA + TBO path end-to-end: it fixes the fused-MoE fallback and tensor lifetimes in the TBO overlap, aligns cross-DP prefill admission with TBO's two-batch requirement, and extends persistent MLA to multi-rank DP.

Technical Details

  • Persistent MLA for DP attention (attention_mla.py): relax use_persistent_mode from "single-rank only" (not (dp_size > 1)) to dp_size <= 8, so persistent MLA also runs in the multi-rank DP configuration used by Kimi K2.5.

  • Fix fused MoE on the DPA fallback path (moe.py, topK.py):

    • In the DP-attn fallback (dp_size > 1, no MORI all2all), MoE runs after all_gather_with_padding, so the token dim can grow to dp_size × the per-rank max. Scale max_num_tokens for the topK / fused-MoE metadata accordingly to avoid undersized buffers.
    • Only select the MORI all2all path when expert parallel is actually enabled (enable_expert_parallel), so DPA-without-EP correctly falls back instead of assuming all2all.
  • Fix TBO tensor live range (moe.py): add a per-(role, ubatch) _TBO_KEEPALIVE holder around the all-gather and reduce-scatter comm/compute switches. Under TBO the source/output tensors of in-flight collectives could be freed before the overlapping ubatch waited on the comm; the keepalive defers release to the next same-role hold, which is past the wait point.

  • Two-batch-aware prefill alignment (scheduler.py, prefill_delayer.py): TBO prefill splitting needs at least two local prefill requests per DP rank. Replace _can_admit_head_prefill (boolean) with _count_admittable_head_prefills(limit) and a _prefill_delayer_readiness() helper that reports both "has any prefill" and "alignment-ready" (>= 2 requests when TBO is on, >= 1 otherwise). PrefillDelayer gains a 4th MAX-reduce slot (local_alignment_ready) so prefill is delayed until every DP rank can launch a full two-batch, not just until one rank has a request.

  • Fix TBO prefill ubatch DP offsets by propagating per-ubatch per-rank token counts through ForwardContext, then rebuilding ubatch-local DPMetadata inside UBatchWrapper. This prevents DP all_gatherv/reduce_scatterv from using full-batch offsets for individual ubatches.

  • Zero MoE all-gather padding rows before fused-MoE routing/sort/dispatch. Padding rows are later sliced away, but they still participate in fused MoE internals; leaving them uninitialized can introduce NaN/Inf garbage, perturb expert buckets/shared scratch, and corrupt real tokens.

Bugfix Validation

Bad TBO run before MoE padding fix: GSM8K flexible 0.8999, with 125 invalid responses and 238 corrupted outputs.
After fix: GSM8K flexible 0.9742, invalid down to 1, corrupted outputs down to 0.

Perf Benchmark Plan

We tested Kimi K2.5 MXFP4 end-to-end inference on MI355X with ROCm 7.2.3, TP4.

The comparison includes:

  • baseline without DPA / TBO
  • DPA only
  • DPA + TBO

Test Results

Numbers in parentheses are throughput/GPU changes relative to the baseline.

Conc baseline throughput/GPU baseline interactivity DPA throughput/GPU DPA interactivity DPA+TBO throughput/GPU DPA+TBO interactivity
4 898.9 104.95 611.7 (-32.0%) 73.53 610.2 (-32.1%) 73.63
8 1502.8 89.61 1061.4 (-29.4%) 65.48 1077.1 (-28.3%) 65.67
16 2045.6 61.85 1653.5 (-19.2%) 51.93 1677.6 (-18.0%) 52.86
32 2964.5 43.46 2542.4 (-14.2%) 38.51 2588.0 (-12.7%) 39.21
64 3946.8 29.06 3761.2 (-4.7%) 28.52 3961.7 (+0.4%) 30.09
128 4984.5 19.81 5133.1 (+3.0%) 21.04 5745.4 (+15.3%) 23.55

At higher concurrency, DPA and TBO show a higher throughput ceiling, with DPA+TBO reaching +15.3% throughput/GPU over the baseline at conc=128.

Submission Checklist

Copilot AI review requested due to automatic review settings June 26, 2026 08:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enables end-to-end Data-Parallel Attention (DPA) + Two-Batch Overlap (TBO) for Kimi K2.5 by fixing DP/TBO micro-batch metadata, strengthening MoE correctness on DP fallback paths, and improving cross-DP prefill admission alignment for TBO’s two-batch requirement.

Changes:

  • Propagates per-ubatch per-rank token counts (ub_tokens_across_dp) through DP sync and ForwardContext, and rebuilds ubatch-local DPMetadata in UBatchWrapper.
  • Fixes MoE correctness/stability under DP fallback + TBO (zero padding rows; scale max token metadata; keepalive tensors across overlapped collectives).
  • Updates prefill alignment logic so PrefillDelayer delays until all DP ranks are “alignment-ready” (>=2 local head prefills when TBO is enabled).

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
atom/utils/tbo/ubatching.py Extends DP sync result to include per-ubatch per-rank token counts for TBO/DP variable-length collectives.
atom/utils/tbo/ubatch_wrapper.py Rebuilds DPMetadata per ubatch using per-ubatch token counts; propagates dp_uniform_decode into ubatch Context.
atom/utils/forward_context.py Adds ub_tokens_across_dp plumbing into ForwardContext / set_forward_context.
atom/model_ops/topK.py Adjusts MORI/all2all gating intended for DPA fallback vs EP mode (but currently has a logic issue).
atom/model_ops/moe.py Zeroes DP all-gather padding rows; scales MoE max token metadata for DP fallback; adds TBO keepalive to prevent premature tensor frees.
atom/model_ops/attention_mla.py Enables persistent MLA for multi-rank DP up to dp_size <= 8.
atom/model_engine/scheduler.py Replaces boolean “prefillable” with counted head-prefill admission and exports both presence + alignment readiness signals.
atom/model_engine/prefill_delayer.py Adds local_alignment_ready and expands MAX-reduce buffer to gate prefill on cross-DP alignment readiness.
atom/model_engine/model_runner.py Threads ub_tokens_across_dp from DP sync into set_forward_context for downstream ubatch/DP metadata.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread atom/model_ops/topK.py Outdated
Comment on lines 70 to 74
and config.enable_expert_parallel
)
if use_mori_all2all:
return False
return True
@valarLip
valarLip requested a review from ZhangLirong-amd June 26, 2026 10:07
Comment thread atom/model_ops/topK.py Outdated
return False
break

dp_size = config.parallel_config.data_parallel_size

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplicate? You can check line 24.
if dp_size > 1 and _has_module("mori") and config.enable_dp_attention: return False

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing out. That's a rebase error (fixed now). Here we try to enable shared expert fusion for DPA for allgather/reducescatter MoE path (not mori all2all).

Comment thread atom/model_ops/moe.py
and not self.moe_parallel_config.use_all2all_kernels
and atom_config.enable_dp_attention
):
moe_max_num_tokens *= self.moe_parallel_config.dp_size

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why we need moe_max_num_tokens *= self.moe_parallel_config.dp_size here.. In all_gahter and model runner, we have padded, * dp_size here will make BS large and kernel bad perf

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here, we only increase the size of the preallocated internal buffer in FusedMoE, not the actual batch size used in the forward pass. This internal buffer needs to be large enough to accommodate tokens from all DP ranks, so we multiply by dp_size, similar to what we've already done for the all-gather / reduce-scatter buffers.

@jpy794
jpy794 force-pushed the rf-dpa-tbo-rebase branch from 31ab320 to 426f176 Compare June 26, 2026 12:49
Copilot AI review requested due to automatic review settings June 26, 2026 13:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread atom/model_engine/scheduler.py Outdated
Comment on lines +537 to +541
@@ -536,9 +538,26 @@ def _can_admit_head_prefill(self) -> bool:
if num_new_tokens > self.max_num_batched_tokens:
continue
if self.block_manager.can_allocate(seq) < 0:
return False # KV-pressured: definitely cannot prefill
return True
return False
break # KV-pressured: definitely cannot prefill more now.
Comment thread atom/model_ops/moe.py
Comment on lines 3406 to +3408
if _tbo:
tbo_switch_to_compute_sync()
self._hold_tbo_keepalive("ag_output", hidden_states, router_logits)
Comment thread atom/model_ops/moe.py
Comment on lines 3443 to +3445
if _tbo:
tbo_switch_to_compute_sync()
self._hold_tbo_keepalive("rs_output", final_hidden_states)
@zufayu
zufayu requested a review from ZhangLirong-amd June 26, 2026 14:01
Comment thread atom/model_engine/prefill_delayer.py Outdated
Mechanism (per scheduler tick):
1. Each DP rank reports its local state via cpu all_gather:
(local_prefillable, watermark_force_allow)
(local_prefillable, local_alignment_ready, watermark_force_allow)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your changes seem to require >=2 bs for TBO to be ready; does this approach has performance improvement? Or whether it will affect old performance.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve made this behavior configurable via an environment variable (disabled by default to avoid affecting existing performance).

Below is a Kimi K2.5 performance comparison (conc=128, isl=8k, osl=1k) with ATOM_PREFILL_DELAYER_REQUIRED_PREFILLS=0/2, about 25% throughput gain observed.

ATOM_PREFILL_DELAYER_REQUIRED_PREFILLS=0

============ Serving Benchmark Result ============
Successful requests:                     512       
Benchmark duration (s):                  235.88    
Total input tokens:                      3775394   
Total generated tokens:                  473911    
Request throughput (req/s):              2.17      
Output token throughput (tok/s):         2009.10   
Total Token throughput (tok/s):          18014.52  
---------------Time to First Token----------------
Mean TTFT (ms):                          2994.52   
Median TTFT (ms):                        786.19    
P99 TTFT (ms):                           17830.07  
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          59.09     
Median TPOT (ms):                        61.84     
P99 TPOT (ms):                           77.20     
---------------Inter-token Latency----------------
Mean ITL (ms):                           59.33     
Median ITL (ms):                         27.85     
P99 ITL (ms):                            626.02    
----------------End-to-end Latency----------------
Mean E2EL (ms):                          57912.88  
Median E2EL (ms):                        58740.95  
P99 E2EL (ms):                           81474.65  
==================================================

ATOM_PREFILL_DELAYER_REQUIRED_PREFILLS=2

============ Serving Benchmark Result ============
Successful requests:                     512       
Benchmark duration (s):                  178.86    
Total input tokens:                      3775394   
Total generated tokens:                  473911    
Request throughput (req/s):              2.86      
Output token throughput (tok/s):         2649.66   
Total Token throughput (tok/s):          23758.06  
---------------Time to First Token----------------
Mean TTFT (ms):                          3435.93   
Median TTFT (ms):                        1459.46   
P99 TTFT (ms):                           16975.80  
-----Time per Output Token (excl. 1st token)------
Mean TPOT (ms):                          43.29     
Median TPOT (ms):                        44.66     
P99 TPOT (ms):                           54.32     
---------------Inter-token Latency----------------
Mean ITL (ms):                           43.39     
Median ITL (ms):                         27.81     
P99 ITL (ms):                            742.68    
----------------End-to-end Latency----------------
Mean E2EL (ms):                          43596.09  
Median E2EL (ms):                        43810.70  
P99 E2EL (ms):                           60233.29  
==================================================

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, that's good news. We will test ATOM_PREFILL_DELAYER_REQUIRED_PREFILLS=2 on deepseek v4 and other models if it's indeed effective

@ZhangLirong-amd

Copy link
Copy Markdown
Collaborator

And could you solve the conflicts and we continue the next step?

Copilot AI review requested due to automatic review settings July 3, 2026 16:28
@jpy794
jpy794 force-pushed the rf-dpa-tbo-rebase branch from 995a56d to 3e64075 Compare July 3, 2026 16:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Comment thread atom/utils/envs.py Outdated
Comment on lines +233 to +236
# Number of local prefill requests required to allow prefill
"ATOM_PREFILL_DELAYER_REQUIRED_PREFILLS": lambda: int(
os.getenv("ATOM_PREFILL_DELAYER_REQUIRED_PREFILLS", "1")
),
Comment thread atom/model_engine/scheduler.py Outdated
Comment on lines +880 to +884
and (
self.prefill_delayer is not None
or self.delay_factor <= 0
or self._passed_delay(time.time())
)
Comment on lines 820 to 823
# A rank counts as "prefillable" for cross-DP alignment only if it
# can admit a prefill AND has a full batch's worth of waiting tokens.
# This makes all ranks align on firing dense prefills together
# instead of straggling partials.
Comment on lines 99 to 104
# Encoding:
# slot 0 = local_prefillable (MAX → "any rank prefillable")
# slot 1 = local_force (MAX → "any rank forces allow")
# slot 2 = NOT local_prefillable (MAX → "any rank lacks prefill")
# slot 2 = NOT local_prefill_sufficient
# (MAX → "any rank lacks required prefill count")
# Then prefillable_status:
@jpy794

jpy794 commented Jul 3, 2026

Copy link
Copy Markdown
Author

@ZhangLirong-amd Hello, I've rebased to main. I'll add more performance data tomorrow.

ATOM config: --no-enable_chunked_prefill --enable-tbo --enable-dp-attention
Benchmark dataset: isl=8k osl=1k random ratio=0.8 conc=128 prompt=512

branch total tok/s output tok/s median TPOT interactivity tok/s/user GSM8K flexible GSM8K strict
rf-dpa-tbo-rebase (delay factor=1) 25424.70 2835.53 34.46 ms 29.02 0.9515 0.9469
main 20367.48 2271.52 50.90 ms 19.65 0.9136 0.9128

Comment thread atom/model_ops/moe.py
)

tbo_yield_and_switch_from_compute_to_comm()
self._hold_tbo_keepalive("ag_source", hidden_states, router_logits)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I have a question, why we need this in all_gather/reduce_scatter with TBO, other models we enabled before didn't meet issues in old logic

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the use-after-free race for the tensor allocated in stream A and used in stream B. Without this fix the intermediate tensor could be reused by pytorch in the allocating stream before its real use by kernels in the other stream.

You can see the difference in gsm8k (0.9136 vs 0.9515) for Kimi k2.5.

I think other models should have the same race issue, not sure if it's because some minor difference in code path hide this race condition.

Race without tbo_keepalive
==========================

Time ─────────────────────────────────────────────────────────────────────>

Compute stream:  produce T ─────────────── drop ref ───── alloc U / reuse T storage
                                      │                         │
                                      │ CPU enqueues AG/RS(T)   │
                                      ▼                         ▼
Comm stream:                         AG/RS reads T ─────────────X
                                                               corrupted / UAF

Copilot AI review requested due to automatic review settings July 12, 2026 08:11
@jpy794
jpy794 force-pushed the rf-dpa-tbo-rebase branch from 3e64075 to d2f9b23 Compare July 12, 2026 08:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Comment on lines 8 to +10
Mechanism (per scheduler tick):
1. Each DP rank reports its local state via cpu all_gather:
(local_prefillable, watermark_force_allow)
(local_prefillable, local_prefill_sufficient, watermark_force_allow)
Comment on lines +1060 to 1064
and gqa_ratio == 64
)
use_persistent_mode = dp_size == 1 or requires_persistent_mode
if envs.ATOM_MLA_PAGE_SIZE > 1:
use_persistent_mode = False
Comment thread tests/test_scheduler.py
def test_threshold_defaults_to_batch_budget(self):
cfg = MockConfig(max_num_batched_tokens=32)
sched = Scheduler(cfg)
# Threshold is derived from the batch-token budget, not a separate knob.
Comment thread atom/model_ops/moe.py
Comment on lines +86 to +87
_TBO_KEEPALIVE: dict[tuple[str, int], tuple[torch.Tensor, ...]] = {}

@jpy794

jpy794 commented Jul 12, 2026

Copy link
Copy Markdown
Author

@ZhangLirong-amd Hi, we have refactored the TBO scheduling optimization based on PR #1437 and included a bug fix for an issue in #1437. With this fix, we observed a 13.6% throughput improvement.

We have also added more ablation results below and provided the corresponding microbenchmark scripts for the tbo keepalive race condition fix. We hope these additions make the changes easier to evaluate and review.

Recently, however, we noticed that PR #1537 reverted #1437 (tbo dp delay gate), while PR #1503 reverted #1474 (dp uniform decode). Since these PRs explore optimization ideas similar to those in our PR, their recent reverts have made it somewhat difficult for us to understand the current upstream direction and how we should proceed.

It is possible that the bug fix in commit 2a5517ea527 could help address the performance issue that led to the revert of #1437.

We would greatly appreciate any guidance on how we could adjust or restructure this PR to make it easier to review and merge. We are also happy to split the changes into multiple smaller PRs if that would better align with the upstream development process.

Please also feel free to reach out to us for an online discussion if that would be more convenient—we would be happy to walk through the design, implementation, and evaluation results in more detail.

Motivation

This PR contains several related TBO changes. If the combined scope is too large for one review, we are happy to discuss splitting it into smaller PRs in the following order:

  1. TBO DP-prefill batch scheduling improvements based on PR fix(scheduler): gate prefill on full batch to protect decode #1437 .
  2. TBO multi-stream race correctness fix.
  3. The remaining TBO performance fixes and Kimi K2.5 support.

This PR completes the TBO work in four areas: DP-prefill scheduling, multi-stream correctness, avoiding an unintended padded all-gather path, and Kimi K2.5 enablement.

PR #1437 has already upstreamed a cross-DP prefill batching strategy similar to the one proposed here, so this PR does not duplicate that work. Instead, this PR provides the following improvements:

  • DP-prefill scheduling: the configurable 10k threshold plus the cross-rank sufficiency fix reaches 25,517 tok/s, 13.6% above the original delay policy's 22,466 tok/s. Compared with the 10k threshold alone, the sufficiency fix improves throughput by 3.45%-4.37% and reduces P99 TTFT by about 25.5% across three paired reruns.
  • Multi-stream correctness: keeping TBO intermediates alive eliminates allocator-reuse corruption in the microbenchmark and restores GSM8K exact match from 91.74% to 97.50%, a 5.76 percentage-point improvement.
  • TBO performance path: preserving dp_uniform_decode prevents uniform decode ubatches from unnecessarily taking the padded all-gather path.
  • Kimi K2.5 TBO support: the persistent MLA and corrected DPA MoE buffer sizing remove the GQA-ratio-64 initialization failure and the 16,384-versus-40,960-token metadata overflow; fused shared experts are also enabled for the non-MORI intra-node DPA path.

Technical Details

1. TBO scheduling optimization

  • 970a64ab adds --prefill-batch-token-threshold, allowing the token threshold used by the prefill delayer to be tuned independently of max_num_batched_tokens. A value of 0 preserves the upstream behavior and uses max_num_batched_tokens as the threshold.
  • 2a5517ea527 fixes incomplete cross-DP alignment in the delayer introduced by fix(scheduler): gate prefill on full batch to protect decode #1437.

Previously, the scheduler folded two different states into local_prefillable: whether the rank could admit any prefill and whether it had accumulated enough waiting tokens for a dense batch. The delayer's cross-rank reduction therefore only distinguished ranks with and without a prefill. Once every rank had at least one admissible request, it released all ranks even if some ranks had only a sparse batch, which left single-request prefills in the schedule.

The fix reports both states explicitly:

  • local_prefillable: at least one prefill can be admitted on this rank.
  • local_prefill_sufficient: an admissible prefill exists and the locally waiting tokens reach the configured dense-batch threshold.

The cross-DP reduction now delays while any rank is not sufficient, while retaining the existing watermark and timeout escape paths. This aligns the release condition across all DP ranks around batch sufficiency rather than mere request availability.

Benchmark configuration: 512 prompts at concurrency 128, random ISL/OSL 8192/1024 with range ratio 0.8, max_num_batched_tokens=16384, chunked prefill and prefix caching disabled, TP=4, seed 0.

Case Duration (s) Total throughput (tok/s) Mean TTFT (ms) P99 TTFT (ms) Mean TPOT (ms)
No delay 312.37 13,603.44 6,670.00 44,822.00 75.68
Original delay, threshold 16,384 189.14 22,466.08 3,724.99 16,042.04 45.61
Threshold 10k (3-run mean) 173.05 24,555.72 3,094.82 19,415.30 42.09
Threshold 10k + sufficient fix (3-run mean) 166.53 25,517.01 3,048.23 14,468.06 40.32

The 10k rows report the arithmetic mean of three interleaved reruns. In particular, they supersede an earlier 186.52s sufficient-fix run that was affected by run order / machine state.

Across the three paired reruns, the sufficient fix reduced duration by 3.33%-4.19%, increased total throughput by 3.45%-4.37%, and reduced P99 TTFT by about 25.5% relative to threshold-only. The batching trace also shows the scheduling effect directly:

Case Prefill batches Requests per batch Mean tokens/batch Median tokens/batch
No delay 437 1: 362, 2: 75 8,639.3 7,560.0
Original delay 280 1: 48, 2: 232 13,483.5 14,606.5
Threshold 10k 273 1: 34, 2: 239 13,829.3 14,602.0
Threshold 10k + sufficient fix 256 2: 256 14,747.6 14,808.5

The sufficient check eliminates the remaining single-request prefill batches and produces 256 consistently dense two-request batches.

2. TBO correctness bug fix

fa5100c5733e fixes a multi-stream lifetime race in the TBO MoE path. TBO switches work between compute and communication streams, but returning from a collective or switching the active stream does not by itself guarantee that every asynchronous consumer has finished reading the input storage. If the last Python reference to an intermediate tensor is released too early, PyTorch's caching allocator may immediately recycle that storage for a new tensor while another stream or peer is still reading it. The result is silent corruption rather than necessarily a launch failure.

The fix keeps the previous all-gather/reduce-scatter input and output tensors alive per TBO ubatch and role. They are released only at a later same-role hold, after the ubatch has crossed the synchronization point for the prior communication work.

The multistream_keepalive_race_microbench.py repro isolates this lifetime rule. It creates an intermediate tensor on the producer/default stream, queues a delayed pointer-based read on a consumer stream, drops the Python reference, and immediately allocates a same-shape poison tensor. The keepalive variant retains the source until an event recorded on the consumer stream completes.

unsafe    ptr_reuse=100/100 corrupt_iters=100/100 corrupt_elements=25600
keepalive ptr_reuse=0/100 corrupt_iters=0/100 corrupt_elements=0
PASS: delayed keepalive prevents cross-stream allocator-reuse corruption

The end-to-end GSM8K result confirms that this is observable model corruption even though the server starts successfully without the fix. On the full 1,319-example, 5-shot, temperature-0 evaluation at concurrency 128:

Version Correct Exact match
With fa5100c 1,286 / 1,319 97.4981% +/- 0.4302%
Revert fa5100c 1,210 / 1,319 91.7362% +/- 0.7584%

Reverting the fix loses 5.7619 percentage points and 76 net correct answers (80 correct-to-wrong and 4 wrong-to-correct).

This race is independent of ROCm/aiter#4082. That issue concerns synchronization inside custom collective kernels before callers reuse peer-read input buffers; this PR fixes a separate ATOM/TBO ownership problem where Python tensor references can expire while asynchronous work on another stream still uses their storage.

3. TBO performance bug fix

906179c63af7 propagates dp_uniform_decode into each TBO ubatch ForwardContext. Without this field, a globally uniform decode batch can be misclassified inside the split TBO context and take the all-gather-with-padding path. Preserving the parent context's uniform-decode state keeps prefill/decode routing consistent and avoids the unnecessary padded all-gather code path.

4. Kimi K2.5 TBO support

  • cfe57e2d enables persistent MLA for DPA configurations up to DP=8. Kimi K2.5 uses GQA ratio 64, which the non-persistent AITER kernel does not support. Reverting this change fails during initialization:

    AITER: fp8/fp8 with gqa_ratio=64 only supports persistent mode
    RuntimeError: Engine Core Mgr received SHUTDOWN during initialization
    
  • ff364f91c728 sizes fused-MoE and top-k metadata buffers for the actual DPA gather/scatter token domain. In the DP-attention fallback without MORI all-to-all, MoE receives the padded all-gather result, whose token dimension can be dp_size * max_num_batched_tokens; reserving only the per-rank budget under-allocates metadata. Reverting the fix reproduces:

    AssertionError: AITER topK meta data support 16384 tokens, but got 40960 tokens
    
  • 1e3ee2e6a0 enables fused shared experts for the intra-node DPA gather/scatter path when MORI all-to-all is not actually in use. The MORI DP+EP layout remains excluded, while the non-MORI path can use the faster fused implementation. The revert ablation did not produce a valid performance comparison because it stalled in the AITER JIT/build-lock path before server readiness.

Test Plan

Run the prefill-delay performance ablation with the following setup and compare both throughput/latency and emitted prefill batch shapes:

  • Concurrency: 128
  • Number of prompts: 512
  • Input/output lengths: random ISL=8192 and OSL=1024, range ratio 0.8
  • max_num_batched_tokens=16384
  • Chunked prefill: disabled
  • Prefix caching: disabled
  • TP=4, seed=0

Cover no delay, the original 16,384-token threshold, the custom 10k threshold, and the 10k threshold with the sufficient fix. Interleave repeated runs of the last two cases to avoid run-order bias.

Run the tensor-lifetime microbenchmark on an otherwise idle GPU:

HIP_VISIBLE_DEVICES=0 python tools/multistream_keepalive_race_microbench.py

Run the full GSM8K evaluation with and without fa5100c, and verify Kimi K2.5 initialization plus inference under TBO/DPA.

@ZhangLirong-amd

Copy link
Copy Markdown
Collaborator

@jpy794 ,sure, please rebase to main and solve the conflict, we will test this branch on dsv4 tbo to make sure its performance.

jpy794 and others added 5 commits July 13, 2026 03:35
* fix(scheduler): gate prefill on full batch to protect decode

Hold new prefills until the waiting queue can fill max_num_batched_tokens,
else keep decoding. Prevents fast 补发 from firing under-full prefills that
preempt decode and drop it out of cudagraph. Tail-escape and pass-budget
valves avoid starvation.

* style: black format

* fix(scheduler): gate dense-batch prefill hold to DP>1 only

The prefill dense-batch gate only helps cross-DP rank alignment. Disable
it when data_parallel_size<=1 so single-GPU/TP-only runs keep the legacy
prefill-first behavior (no added TTFT).

---------

Co-authored-by: ZhangLirong-amd <ZhangLirong@amd.com>
Copilot AI review requested due to automatic review settings July 13, 2026 05:06
@jpy794
jpy794 force-pushed the rf-dpa-tbo-rebase branch from d2f9b23 to 56d3dda Compare July 13, 2026 05:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@jpy794

jpy794 commented Jul 13, 2026

Copy link
Copy Markdown
Author

@ZhangLirong-amd Hi, I've rebased to main with changes from #1437 included. I'm also glad to help run some dsv4 TBO benchmark to verify the performance, if you could provide some detailed benchmark setup.

@ZhangLirong-amd

Copy link
Copy Markdown
Collaborator

@ZhangLirong-amd Hi, I've rebased to main with changes from #1437 included. I'm also glad to help run some dsv4 TBO benchmark to verify the performance, if you could provide some detailed benchmark setup.

Sure, you can rty

GPU_MAX_HW_QUEUES=5 ATOM_DISABLE_MMAP=true AITER_BF16_FP8_MOE_BOUND=0 ATOM_MOE_GU_ITLV=1 python -m atom.entrypoints.openai_server   --model /data/models/DeepSeek-V4-Pro/   --kv_cache_dtype fp8   -tp 8   --gpu-memory-utilization 0.85   --server-port 7777  --torch-profiler-dir ./log --enable-dp-attention --enable-tbo


python -m atom.benchmarks.benchmark_serving   --model=/data/models/DeepSeek-V4-Pro/ --backend=vllm --base-url=http://localhost:7777   --dataset-name=random --random-input-len=8192 --random-output-len=1024 --random-range-ratio 0.8   --num-prompts=10240 --max-concurrency=1024   --request-rate=inf --ignore-eos

@ZhangLirong-amd

Copy link
Copy Markdown
Collaborator

@jpy794 ,hi, seems I meet regression, Total Token throughput (tok/s): 42820.94, in nightly benchmark ,it's about 48000`49000

@benenzhu

benenzhu commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

For kimi for conc128 + TP4, though haven't tested on other models.
https://inferencex.semianalysis.com/inference?unofficialRun=29234093839&i_disagg=agg
https://github.com/SemiAnalysisAI/InferenceX/actions/runs/29234093839/job/86764699211

Metric Before After Improvement
Throughput 5370 5971 +11.19%
Interactivity 19.16 25.20 +31.52%

With configs: SemiAnalysisAI/InferenceX@main...amd/zty_test3

    --enable-dp-attention \
    --enable-tbo \
    --prefill-batch-token-threshold 10240 \
image

@jpy794

jpy794 commented Jul 13, 2026

Copy link
Copy Markdown
Author

I could also reproduce the regession in dsv4. Total throughput dropped from 19,345.53 to 18,940.36 with tp8, conc=128, prompts=512. I'm currently investigating the root cause.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants